<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
  <title>PMTiles Shaded Relief</title>
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css"
    crossorigin="anonymous">
  <link rel="stylesheet" type="text/css"
    href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.2/css/fontawesome.min.css"
    crossorigin="anonymous">
  <link rel="stylesheet" type="text/css"
    href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.2/css/solid.css" crossorigin="anonymous">
  <link rel="stylesheet" type="text/css"
    href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.2/css/brands.css" crossorigin="anonymous">
  <link rel="stylesheet" type="text/css" href="./theme/ol.css">
  <link rel="stylesheet" type="text/css" href="/theme/site.css">
  <link rel="stylesheet" type="text/css" href="pmtiles-elevation.css">
  <link rel="icon" type="image/svg+xml" href="/theme/img/logo-light.svg" media="(prefers-color-scheme: light)" />
  <link rel="icon" type="image/svg+xml" href="/theme/img/logo-dark.svg" media="(prefers-color-scheme: dark)" />
</head>

<body>
  <header class="navbar navbar-expand-md navbar-dark mb-3 px-3 py-0 fixed-top" role="navigation">
    <a class="navbar-brand" href="/"><img src="/theme/img/logo-dark.svg" width="70" height="70"
        alt="">&nbsp;OpenLayers</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#olmenu"
      aria-controls="olmenu" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>

    <!-- menu items that get hidden below 768px width -->
    <nav class="collapse navbar-collapse" id="olmenu">
      <ul class="nav navbar-nav ms-auto">
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="docdropdown" role="button" data-bs-toggle="dropdown"
            aria-haspopup="true" aria-expanded="false">Docs</a>
          <div class="dropdown-menu dropdown-menu-end mb-3" aria-labelledby="docdropdown">
            <a class="dropdown-item" href="/doc/">Docs</a>
            <div class="dropdown-divider"></div>
            <a class="dropdown-item" href="/doc/quickstart.html"><i class="fa fa-check fa-fw me-2 fa-lg"></i>快速入门</a>
            <a class="dropdown-item" href="/doc/faq.html"><i class="fa fa-question fa-fw me-2 fa-lg"></i>常见问题</a>
            <a class="dropdown-item" href="/doc/tutorials/"><i class="fa fa-book fa-fw me-2 fa-lg"></i>教程</a>

          </div>
        </li>
        <li class="nav-item"><a class="nav-link" href="/en/latest/examples/">Examples</a></li>
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="apidropdown" role="button" data-bs-toggle="dropdown"
            aria-haspopup="true" aria-expanded="false">
            <i class="fa fa-sitemap me-1"></i>API
          </a>
          <div class="dropdown-menu dropdown-menu-end mb-3" aria-labelledby="apidropdown">
            <a class="dropdown-item" href="/en/latest/apidoc/"><i class="fa fa-sitemap fa-fw me-2 fa-lg"></i>v8.2.0
              (latest)</a </div>
        </li>
      </ul>
    </nav>
  </header>

  <div class="container-fluid line-numbers">
    <div id="latest-check" class="alert alert-warning alert-dismissible" role="alert" style="display:none">
      <button id="latest-dismiss" type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
      This example uses OpenLayers v<span>8.2.0</span>. The <a id="latest-link" href="#" class="alert-link">latest</a>
      is v<span id="latest-version"></span>.
    </div>


    <div class="row-fluid">
      <a href="#" id="codepen-button" class="btn btn-link float-end">
        <i class="fab fa-codepen fa-lg"></i> Edit
      </a>
      <div class="span12">
        <h4 id="title">PMTiles Shaded Relief</h4>
        <p class="tags">
          <span class="badge-group">
            <a href="./index.html?q=pmtiles" class="badge badge-info">pmtiles</a><a
              class="badge badge-info tag-modal-toggle" data-bs-toggle="modal" data-bs-target="#tag-example-list"
              data-title="pmtiles" data-content="&lt;ul class=&quot;list-group&quot;&gt;
                  &lt;li&gt;&lt;a class=&quot;list-group-item list-group-item-action active&quot; href=&quot;./pmtiles-elevation.html&quot;&gt;PMTiles Shaded Relief&lt;/a&gt;
                  &lt;/ul&gt;" tabindex="0">1</a>
          </span>
          <span class="badge-group">
            <a href="./index.html?q=elevation" class="badge badge-info">elevation</a><a
              class="badge badge-info tag-modal-toggle" data-bs-toggle="modal" data-bs-target="#tag-example-list"
              data-title="elevation" data-content="&lt;ul class=&quot;list-group&quot;&gt;
                  &lt;li&gt;&lt;a class=&quot;list-group-item list-group-item-action active&quot; href=&quot;./pmtiles-elevation.html&quot;&gt;PMTiles Shaded Relief&lt;/a&gt;
                  &lt;/ul&gt;" tabindex="0">1</a>
          </span>
          <span class="badge-group">
            <a href="./index.html?q=datatiles" class="badge badge-info">datatiles</a><a
              class="badge badge-info tag-modal-toggle" data-bs-toggle="modal" data-bs-target="#tag-example-list"
              data-title="datatiles" data-content="&lt;ul class=&quot;list-group&quot;&gt;
                  &lt;li&gt;&lt;a class=&quot;list-group-item list-group-item-action&quot; href=&quot;./data-tiles.html&quot;&gt;Data Tiles&lt;/a&gt;
                  &lt;li&gt;&lt;a class=&quot;list-group-item list-group-item-action active&quot; href=&quot;./pmtiles-elevation.html&quot;&gt;PMTiles Shaded Relief&lt;/a&gt;
                  &lt;/ul&gt;" tabindex="0">2</a>
          </span>
        </p>
        <div class="modal modal-tag-example" id="tag-example-list" tabindex="-1" role="dialog"
          aria-labelledby="tag-example-title" aria-hidden="true">
          <div class="modal-dialog modal-dialog-scrollable" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <h5 class="modal-title" id="tag-example-title"></h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
              </div>
              <div class="modal-body">
                <div class="list-group"></div>
              </div>
            </div>
          </div>
        </div>
        <div id="map" class="map"></div>
        <table class="controls">
          <tr>
            <td>elevation</td>
            <td colspan="2" id="elevationOut"></td>
          </tr>
          <tr>
            <td>location</td>
            <td colspan="2" id="locationOut"></td>
          </tr>
          <tr>
            <td><label for="vert">vertical exaggeration:</label></td>
            <td><input id="vert" type="range" min="5" max="50" value="10" /></td>
            <td><span id="vertOut"></span> x</td>
          </tr>
          <tr>
            <td><label for="sunEl">sun elevation:</label></td>
            <td><input id="sunEl" type="range" min="0" max="90" value="45" /></td>
            <td><span id="sunElOut"></span> °</td>
          </tr>
          <tr>
            <td><label for="sunAz">sun azimuth:</label></td>
            <td><input id="sunAz" type="range" min="0" max="360" value="45" /></td>
            <td><span id="sunAzOut"></span> °</td>
          </tr>
        </table>

      </div>
      <form method="POST" id="codepen-form" target="_blank" action="https://codesandbox.io/api/v1/sandboxes/define">
        <input id="codesandbox-params" type="hidden" name="parameters">
      </form>
    </div>

    <div class="row-fluid">
      <div class="span12">
        <p id="shortdesc">Elevation data from PMTiles.</p>
        <div id="docs">
          <p>This example shows a shaded relief rendering of elevation data from a <a
              href="https://github.com/protomaps/PMTiles">PMTiles</a> source.</p>
        </div>
      </div>
    </div>

    <div class="row-fluid">
      <h5 class="source-heading">main.js</h5>
      <pre><code id="example-js-source" class="language-js">/* global pmtiles */
import DataTile from &#x27;ol/source/DataTile.js&#x27;;
import Map from &#x27;ol/Map.js&#x27;;
import TileLayer from &#x27;ol/layer/WebGLTile.js&#x27;;
import View from &#x27;ol/View.js&#x27;;
import {useGeographic} from &#x27;ol/proj.js&#x27;;

useGeographic();

const tiles &#x3D; new pmtiles.PMTiles(
  &#x27;https://pub-9288c68512ed46eca46ddcade307709b.r2.dev/protomaps-sample-datasets/terrarium_z9.pmtiles&#x27;
);

function loadImage(src) {
  return new Promise((resolve, reject) &#x3D;&gt; {
    const img &#x3D; new Image();
    img.addEventListener(&#x27;load&#x27;, () &#x3D;&gt; resolve(img));
    img.addEventListener(&#x27;error&#x27;, () &#x3D;&gt; reject(new Error(&#x27;load failed&#x27;)));
    img.src &#x3D; src;
  });
}

async function loader(z, x, y) {
  const response &#x3D; await tiles.getZxy(z, x, y);
  const blob &#x3D; new Blob([response.data]);
  const src &#x3D; URL.createObjectURL(blob);
  const image &#x3D; await loadImage(src);
  URL.revokeObjectURL(src);
  return image;
}

// The method used to extract elevations from the DEM.
//   red * 256 + green + blue / 256 - 32768
function elevation(xOffset, yOffset) {
  const red &#x3D; [&#x27;band&#x27;, 1, xOffset, yOffset];
  const green &#x3D; [&#x27;band&#x27;, 2, xOffset, yOffset];
  const blue &#x3D; [&#x27;band&#x27;, 3, xOffset, yOffset];

  // band math operates on normalized values from 0-1
  // so we scale by 255
  return [
    &#x27;+&#x27;,
    [&#x27;*&#x27;, 255 * 256, red],
    [&#x27;*&#x27;, 255, green],
    [&#x27;*&#x27;, 255 / 256, blue],
    -32768,
  ];
}

// Generates a shaded relief image given elevation data.  Uses a 3x3
// neighborhood for determining slope and aspect.
const dp &#x3D; [&#x27;*&#x27;, 2, [&#x27;resolution&#x27;]];
const z0x &#x3D; [&#x27;*&#x27;, [&#x27;var&#x27;, &#x27;vert&#x27;], elevation(-1, 0)];
const z1x &#x3D; [&#x27;*&#x27;, [&#x27;var&#x27;, &#x27;vert&#x27;], elevation(1, 0)];
const dzdx &#x3D; [&#x27;/&#x27;, [&#x27;-&#x27;, z1x, z0x], dp];
const z0y &#x3D; [&#x27;*&#x27;, [&#x27;var&#x27;, &#x27;vert&#x27;], elevation(0, -1)];
const z1y &#x3D; [&#x27;*&#x27;, [&#x27;var&#x27;, &#x27;vert&#x27;], elevation(0, 1)];
const dzdy &#x3D; [&#x27;/&#x27;, [&#x27;-&#x27;, z1y, z0y], dp];
const slope &#x3D; [&#x27;atan&#x27;, [&#x27;sqrt&#x27;, [&#x27;+&#x27;, [&#x27;^&#x27;, dzdx, 2], [&#x27;^&#x27;, dzdy, 2]]]];
const aspect &#x3D; [&#x27;clamp&#x27;, [&#x27;atan&#x27;, [&#x27;-&#x27;, 0, dzdx], dzdy], -Math.PI, Math.PI];
const sunEl &#x3D; [&#x27;*&#x27;, Math.PI / 180, [&#x27;var&#x27;, &#x27;sunEl&#x27;]];
const sunAz &#x3D; [&#x27;*&#x27;, Math.PI / 180, [&#x27;var&#x27;, &#x27;sunAz&#x27;]];

const incidence &#x3D; [
  &#x27;+&#x27;,
  [&#x27;*&#x27;, [&#x27;sin&#x27;, sunEl], [&#x27;cos&#x27;, slope]],
  [&#x27;*&#x27;, [&#x27;cos&#x27;, sunEl], [&#x27;sin&#x27;, slope], [&#x27;cos&#x27;, [&#x27;-&#x27;, sunAz, aspect]]],
];

const variables &#x3D; {};

const layer &#x3D; new TileLayer({
  source: new DataTile({
    loader,
    wrapX: true,
    maxZoom: 9,
    attributions:
      &quot;&lt;a href&#x3D;&#x27;https://github.com/tilezen/joerd/blob/master/docs/attribution.md#attribution&#x27;&gt;Tilezen Jörð&lt;/a&gt;&quot;,
  }),
  style: {
    variables: variables,
    color: [&#x27;array&#x27;, incidence, incidence, incidence, 1],
  },
});

const controlIds &#x3D; [&#x27;vert&#x27;, &#x27;sunEl&#x27;, &#x27;sunAz&#x27;];
controlIds.forEach(function (id) {
  const control &#x3D; document.getElementById(id);
  const output &#x3D; document.getElementById(id + &#x27;Out&#x27;);
  function updateValues() {
    output.innerText &#x3D; control.value;
    variables[id] &#x3D; Number(control.value);
  }
  updateValues();
  control.addEventListener(&#x27;input&#x27;, function () {
    updateValues();
    layer.updateStyleVariables(variables);
  });
});

const map &#x3D; new Map({
  target: &#x27;map&#x27;,
  layers: [layer],
  view: new View({
    center: [0, 0],
    zoom: 1,
  }),
});

function getElevation(data) {
  const red &#x3D; data[0];
  const green &#x3D; data[1];
  const blue &#x3D; data[2];
  return red * 256 + green + blue / 256 - 32768;
}

function formatLocation([lon, lat]) {
  const NS &#x3D; lat &lt; 0 ? &#x27;S&#x27; : &#x27;N&#x27;;
  const EW &#x3D; lon &lt; 0 ? &#x27;W&#x27; : &#x27;E&#x27;;
  return &#x60;${Math.abs(lat).toFixed(1)}° ${NS}, ${Math.abs(lon).toFixed(
    1
  )}° ${EW}&#x60;;
}

const elevationOut &#x3D; document.getElementById(&#x27;elevationOut&#x27;);
const locationOut &#x3D; document.getElementById(&#x27;locationOut&#x27;);
function displayPixelValue(event) {
  const data &#x3D; layer.getData(event.pixel);
  if (!data) {
    return;
  }
  elevationOut.innerText &#x3D; getElevation(data).toLocaleString() + &#x27; m&#x27;;
  locationOut.innerText &#x3D; formatLocation(event.coordinate);
}
map.on([&#x27;pointermove&#x27;, &#x27;click&#x27;], displayPixelValue);
</code></pre>
    </div>

    <div class="row-fluid">
      <h5 class="source-heading">index.html</h5>
      <pre><code id="example-html-source" class="language-markup">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;PMTiles Shaded Relief&lt;/title&gt;
    &lt;link rel="stylesheet" href="node_modules/ol/ol.css"&gt;
    &lt;style&gt;
      .map {
        width: 100%;
        height: 400px;
      }
      table.controls td {
        padding: 2px 5px;
      }
      table.controls td:nth-child(3) {
        text-align: right;
        min-width: 3em;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id&#x3D;&quot;map&quot; class&#x3D;&quot;map&quot;&gt;&lt;/div&gt;
    &lt;table class&#x3D;&quot;controls&quot;&gt;
      &lt;tr&gt;
        &lt;td&gt;elevation&lt;/td&gt;
        &lt;td colspan&#x3D;&quot;2&quot; id&#x3D;&quot;elevationOut&quot;&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;location&lt;/td&gt;
        &lt;td colspan&#x3D;&quot;2&quot; id&#x3D;&quot;locationOut&quot;&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;label for&#x3D;&quot;vert&quot;&gt;vertical exaggeration:&lt;/label&gt;&lt;/td&gt;
        &lt;td&gt;&lt;input id&#x3D;&quot;vert&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;5&quot; max&#x3D;&quot;50&quot; value&#x3D;&quot;10&quot;/&gt;&lt;/td&gt;
        &lt;td&gt;&lt;span id&#x3D;&quot;vertOut&quot;&gt;&lt;/span&gt; x&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;label for&#x3D;&quot;sunEl&quot;&gt;sun elevation:&lt;/label&gt;&lt;/td&gt;
        &lt;td&gt;&lt;input id&#x3D;&quot;sunEl&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;0&quot; max&#x3D;&quot;90&quot; value&#x3D;&quot;45&quot;/&gt;&lt;/td&gt;
        &lt;td&gt;&lt;span id&#x3D;&quot;sunElOut&quot;&gt;&lt;/span&gt; °&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;label for&#x3D;&quot;sunAz&quot;&gt;sun azimuth:&lt;/label&gt;&lt;/td&gt;
        &lt;td&gt;&lt;input id&#x3D;&quot;sunAz&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;0&quot; max&#x3D;&quot;360&quot; value&#x3D;&quot;45&quot;/&gt;&lt;/td&gt;
        &lt;td&gt;&lt;span id&#x3D;&quot;sunAzOut&quot;&gt;&lt;/span&gt; °&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/table&gt;

    &lt;script src="https://unpkg.com/pmtiles@2.4.0/dist/index.js"&gt;&lt;/script&gt;
    &lt;script type="module" src="main.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
    </div>

    <div class="row-fluid">
      <h5 class="source-heading">package.json</h5>
      <pre><code id="example-pkg-source" class="language-json">{
  &quot;name&quot;: &quot;pmtiles-elevation&quot;,
  &quot;dependencies&quot;: {
    &quot;ol&quot;: &quot;8.2.0&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;vite&quot;: &quot;^3.2.3&quot;
  },
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;vite&quot;,
    &quot;build&quot;: &quot;vite build&quot;
  }
}</code></pre>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
  <script src="https://unpkg.com/pmtiles@2.4.0/dist/index.js"></script>
  <script src="common.js"></script>
  <script src="pmtiles-elevation.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/components/prism-core.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/autoloader/prism-autoloader.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/toolbar/prism-toolbar.min.js"></script>
  <script
    src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.js"></script>
  <script src="./resources/common.js"></script>
  <script>
    document.getElementById('tag-example-list')
      .addEventListener('show.bs.modal', function (event) {
        const button = event.relatedTarget; // Button that triggered the modal
        this.querySelector('.modal-title').innerText = button.dataset.title;
        this.querySelector('.modal-body').innerHTML = button.dataset.content;
      });
  </script>
  <script>
    const releaseUrl = 'https://cdn.jsdelivr.net/npm/ol/package.json';
    fetch(releaseUrl).then(function (response) {
      return response.json();
    }).then(function (json) {
      const latestVersion = json.version;
      document.getElementById('latest-version').innerHTML = latestVersion;
      const url = window.location.href;
      const branchSearch = url.match(/\/([^\/]*)\/examples\//);
      const storageKey = 'ol-dismissed=-' + latestVersion;
      const dismissed = localStorage.getItem(storageKey) === 'true';
      if (branchSearch && !dismissed && /^v[0-9\.]*$/.test(branchSearch[1]) && '8.2.0' != latestVersion) {
        const link = url.replace(branchSearch[0], '/latest/examples/');
        fetch(link, { method: 'head' }).then(function (response) {
          const a = document.getElementById('latest-link');
          a.href = response.status == 200 ? link : '../../latest/examples/';
        });
        const latestCheck = document.getElementById('latest-check');
        latestCheck.style.display = '';
        document.getElementById('latest-dismiss').onclick = function () {
          latestCheck.style.display = 'none';
          localStorage.setItem(storageKey, 'true');
        }
      }
    });
  </script>
</body>

</html>